/**
 * 
 */
package com.antilia.infomaresme;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.wicket.util.listener.ChangeListenerSet;
import org.apache.wicket.util.listener.IChangeListener;
import org.apache.wicket.util.time.Duration;
import org.apache.wicket.util.time.Time;
import org.apache.wicket.util.watch.IModifiable;
import org.apache.wicket.util.watch.IModificationWatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Monitors one or more <code>IModifiable</code> objects, calling a {@link IChangeListener
 * IChangeListener} when a given object's modification time changes.
 * 
 * @author Jonathan Locke
 * @author Ernesto Reinaldo Barreiro
 * @since 1.2.6
 */
public class ModificationWatcher implements IModificationWatcher
{
	/** logger */
	private static final Logger log = LoggerFactory.getLogger(ModificationWatcher.class);

	/** maps <code>IModifiable</code> objects to <code>Entry</code> objects */
	private final Map<IModifiable, Entry> modifiableToEntry = new ConcurrentHashMap<IModifiable, Entry>();

	/**
	 * Container class for holding modifiable entries to watch.
	 */
	private static final class Entry
	{
		// The most recent lastModificationTime polled on the object
		Time lastModifiedTime;

		// The set of listeners to call when the modifiable changes
		final ChangeListenerSet listeners = new ChangeListenerSet();

		// The modifiable thing
		IModifiable modifiable;
	}

	/**
	 * Default constructor for two-phase construction.
	 */
	public ModificationWatcher()
	{
	}

	/**
	 * Constructor that accepts a <code>Duration</code> argument representing the poll frequency.
	 * 
	 * @param pollFrequency
	 *            how often to check on <code>IModifiable</code>s
	 */
	public ModificationWatcher(final Duration pollFrequency)
	{
		start(pollFrequency);
	}

	/**
	 * @see org.apache.wicket.util.watch.IModificationWatcher#add(org.apache.wicket.util.watch.IModifiable,
	 *      org.apache.wicket.util.listener.IChangeListener)
	 */
	public final boolean add(final IModifiable modifiable, final IChangeListener listener)
	{
		// Look up entry for modifiable
		final Entry entry = modifiableToEntry.get(modifiable);

		// Found it?
		if (entry == null)
		{
			Time lastModifiedTime = modifiable.lastModifiedTime();
			if (lastModifiedTime != null)
			{
				// Construct new entry
				final Entry newEntry = new Entry();

				newEntry.modifiable = modifiable;
				newEntry.lastModifiedTime = lastModifiedTime;
				newEntry.listeners.add(listener);

				// Put in map
				modifiableToEntry.put(modifiable, newEntry);
			}
			else
			{
				// The IModifiable is not returning a valid lastModifiedTime
				log.info("Cannot track modifications to resource " + modifiable);
			}

			return true;
		}
		else
		{
			// Add listener to existing entry
			return entry.listeners.add(listener);
		}
	}

	/**
	 * @see org.apache.wicket.util.watch.IModificationWatcher#remove(org.apache.wicket.util.watch.IModifiable)
	 */
	public IModifiable remove(final IModifiable modifiable)
	{
		final Entry entry = modifiableToEntry.remove(modifiable);
		if (entry != null)
		{
			return entry.modifiable;
		}
		return null;
	}

	/**
	 * @see org.apache.wicket.util.watch.IModificationWatcher#start(org.apache.wicket.util.time.Duration)
	 */
	public void start(final Duration pollFrequency)
	{
		final Iterator<Entry> itor = modifiableToEntry.values().iterator();
		while (itor.hasNext())
		{
			final Entry entry = itor.next();

			// If the modifiable has been modified after the last known
			// modification time
			final Time modifiableLastModified = entry.modifiable.lastModifiedTime();
			if ((modifiableLastModified != null) &&
				modifiableLastModified.after(entry.lastModifiedTime))
			{
				// Notify all listeners that the modifiable was modified
				entry.listeners.notifyListeners();

				// Update timestamp
				entry.lastModifiedTime = modifiableLastModified;
			}
		}
	}

	/**
	 * @see org.apache.wicket.util.watch.IModificationWatcher#destroy()
	 */
	public void destroy()
	{
	}

	/**
	 * @see org.apache.wicket.util.watch.IModificationWatcher#getEntries()
	 */
	public final Set<IModifiable> getEntries()
	{
		return modifiableToEntry.keySet();
	}
}

/*
 * Copyright (c) 2010 INQLING, LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */


